﻿Option Strict On
Option Compare Binary

Imports System.IO
Imports Ionic.Zip
Imports <xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
Imports <xmlns:cih="ContentInstallerHistory">
Imports System.Collections.ObjectModel
Imports System.Xml


''' <summary>
''' Implement helper methods and properties for creating and uninstalling Visual Studio contentes
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class ContentInstaller

    Private Shared _contentVersion As String = String.Empty

    Private Shared _dotNet4Sdk As String = String.Empty

    Private Shared _visualStudio2010Path As String = String.Empty

    Private Shared _visualBasic2010Path As String = String.Empty

    Private Shared _visualCSharp2010Path As String = String.Empty

    Private Shared _isVisualStudioContentInstallerHistoryAvailable As Boolean = False
    Shared contentHistoryFolder As String

    Public Shared ReadOnly Property IsVisualStudioContentInstallerHistoryAvailable() As Boolean
        Get
            _isVisualStudioContentInstallerHistoryAvailable = IO.File.Exists(My.Computer.FileSystem.SpecialDirectories.MyDocuments + "\visual studio 2005\ContentInstallerHistory.xml")
            Return _isVisualStudioContentInstallerHistoryAvailable
        End Get
    End Property

    Public Shared ReadOnly Property ContentsToUninstall As ObservableCollection(Of ContentToUninstall)
        Get
            Try
                Return GetInstalledContentsForUninstall()
            Catch ex As Exception
                Return Nothing
            End Try
        End Get
    End Property


    Private Shared Function getVisualStudio2010Dir() As String

        Try
            _visualStudio2010Path = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0", "InstallDir", "").ToString
            _visualBasic2010Path = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\Software\Microsoft\VBExpress\10.0", "InstallDir", "").ToString
            _visualCSharp2010Path = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\Software\Microsoft\VCSExpress\10.0", "InstallDir", "").ToString
        Catch
        End Try

        'If the folder of Visual Studio 2010 Pro, Premium, Ultimate is not found...
        If String.IsNullOrEmpty(_visualStudio2010Path) = False Then
            Return _visualStudio2010Path
        End If

        '...looks for Express editions' folders
        If String.IsNullOrEmpty(_visualBasic2010Path) = False Then
            Return _visualBasic2010Path
        End If

        If String.IsNullOrEmpty(_visualCSharp2010Path) = False Then
            Return _visualCSharp2010Path
        End If

        Return Nothing

    End Function


    ''' <summary>
    ''' Gets the folder in which Visual Studio 2010 is installed
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared ReadOnly Property VisualStudio2010InstallDir As String
        Get
            Return getVisualStudio2010Dir()
        End Get
    End Property


    ''' <summary>
    ''' Gets the folder in which Windows SDK for .NET 4.0 is installed
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared ReadOnly Property DotNet4SdkInstallDir As String
        Get
            Try
                _dotNet4Sdk = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0A", "InstallationFolder", "").ToString
                Return _dotNet4Sdk
            Catch
                Return String.Empty
            End Try

        End Get
    End Property

    Private Sub New()

    End Sub

    ''' <summary>
    ''' Returns a list of contents that can be uninstalled
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function GetInstalledContentsForUninstall() As ObservableCollection(Of ContentToUninstall)
        If ContentInstaller.IsVisualStudioContentInstallerHistoryAvailable = False Then
            Throw New InvalidOperationException("The ContentInstallerHistory.xml file is not available")
        Else
            Try
                Dim reader As XmlReader

                'this instance of XmlTextReader points to the ContentInstallerHistory.xml
                'which contains the list of installed contents. Both VS 2008 and VS 2010 still store
                'the list in that file
                reader = New XmlTextReader(My.Computer.FileSystem.SpecialDirectories.MyDocuments + "\visual studio 2005\ContentInstallerHistory.xml")
                contentHistoryFolder = My.Computer.FileSystem.SpecialDirectories.MyDocuments + "\visual studio 2005\ContentInstallerHistory.xml"

                'represents a single item to be uninstalled
                Dim singleContent As ContentToUninstall = Nothing

                Dim contentsList As New ObservableCollection(Of ContentToUninstall)

                'NOTICE THAT LINQ TO XML CANNOT BE USED DUE TO THE STRUCTURE OF THE XML FILE

                'loops until the end of the Xml file
                Do While (reader.Read())

                    Select Case reader.NodeType
                        'if the node is an XML element
                        Case XmlNodeType.Element

                            'checks whether it has attributes
                            If reader.HasAttributes Then
                                'if yes, reads and stores them for later usage
                                singleContent = New ContentToUninstall
                                While reader.MoveToNextAttribute()
                                    If LCase(reader.Name) = "type" Then
                                        singleContent.Type = reader.Value
                                    ElseIf LCase(reader.Name) = "name" Then
                                        singleContent.Name = reader.Value
                                    ElseIf LCase(reader.Name) = "timestamp" Then
                                        singleContent.TimeStamp = reader.Value
                                    ElseIf LCase(reader.Name) = "description" Then
                                        singleContent.Description = reader.Value
                                    End If
                                End While
                            End If

                            'if it is a node of pure text, in this specific file the node
                            'stores the path where the content is actually installed.
                        Case XmlNodeType.Text
                            If singleContent IsNot Nothing Then
                                singleContent.InstalledPath = reader.Value
                                contentsList.Add(singleContent)
                            End If


                        Case XmlNodeType.EndElement
                    End Select

                Loop

                'Don't forget to close the stream
                reader.Close()
                Return contentsList
            Catch ex As IO.FileNotFoundException
                Throw New FileNotFoundException("Unable to find the file containing the list of installed content items. Probably no additional content items have been installed yet.")
            Catch ex As IO.DirectoryNotFoundException
                Throw New DirectoryNotFoundException("Unable to find the file containing the list of installed content items. Probably no additional content items have been installed yet.")
            Catch ex As Exception
                Throw
            End Try

        End If
    End Function

    ''' <summary>
    ''' Permanently removes the specified contents from disk
    ''' </summary>
    ''' <param name="contents">A collection of ContentToUninstall items</param>
    ''' <remarks></remarks>
    Public Shared Sub UninstallContents(ByVal contents As IEnumerable(Of ContentToUninstall))

        Dim gotErrors As Boolean 'in case of errors this is set to True

        'Warning in case no elements is in the list
        If contents.Count < 1 Then
            Throw New InvalidOperationException("No content selected yet.")
            Exit Sub
        End If

        'a temporary unique file name that stores the updated list of contents
        Dim temp As String = IO.Path.GetTempFileName

        'opens the temp file for writing
        Dim rew As New IO.StreamWriter(temp)

        Dim tipo As String = "", timestamp As String = "", name As String = "", description As String = ""

        'writes the XML structure for contents
        rew.WriteLine("<VSContentInstallerHistory>")

        'for each content to uninstall...
        For Each item As ContentToUninstall In ContentsToUninstall
            Dim insPath = item.InstalledPath
            If contents.SingleOrDefault(Function(f) f.InstalledPath = insPath) IsNot Nothing Then
                Try
                    'if found, then removes it
                    IO.File.Delete(item.InstalledPath)
                    ContentsToUninstall.Remove(item)
                Catch ex As Exception
                    'if an error occurs in deletion, the file is not deleted so sets the control variable as True
                    'and rewrites the content to the history file
                    gotErrors = True
                    name = item.Name
                    tipo = item.Type
                    timestamp = item.TimeStamp
                    description = item.Description

                    rew.WriteLine("  <ContentItem Type=""" + tipo + """ TimeStamp=""" + timestamp + """ Name=""" + name + """ Description=""" + description + """>")
                    rew.WriteLine("    <InstalledFilePath>" + item.InstalledPath + "</InstalledFilePath>")
                    rew.WriteLine("  </ContentItem>")
                End Try

            Else
                'if the item is not marked for deletion, adds it again to the history file
                name = item.Name
                tipo = item.Type
                timestamp = item.TimeStamp
                description = item.Description

                rew.WriteLine("  <ContentItem Type=""" + tipo + """ TimeStamp=""" + timestamp + """ Name=""" + name + """ Description=""" + description + """>")
                rew.WriteLine("    <InstalledFilePath>" + item.InstalledPath + "</InstalledFilePath>")
                rew.WriteLine("  </ContentItem>")

            End If
        Next

        'the closing XML node
        rew.WriteLine("</VSContentInstallerHistory>")
        rew.Close()


        Try
            'deletes the old ContentInstallerHistory.xml....
            IO.File.Delete(contentHistoryFolder)
            'moves the temp file to the correct VS 2005 folder
            IO.File.Move(temp, contentHistoryFolder)
            'finally renames the file and sets it as hidden
                        IO.File.SetAttributes(contentHistoryFolder, IO.FileAttributes.Hidden)
        Catch ex As Exception
            'if any errors, leaves the old file and creates a copy of the new one in the same
            'location, with a different name so that data are not lost (they need to be compared manually at this point)
            Dim backupFile As String
            backupFile = IO.Path.ChangeExtension(contentHistoryFolder, ".new")
            IO.File.Move(temp, backupFile)

            Throw
        End Try

        'Throws an exception if the operation failed
        If gotErrors = True Then
            Throw New InvalidOperationException("Uninstall finished but some errors occurred. Probably some content items could not be removed.")
        End If

    End Sub

End Class

''' <summary>
''' Represents a single content that can be uninstalled
''' </summary>
''' <remarks></remarks>
Public Class ContentToUninstall
    Public Property Description As String
    Public Property TimeStamp As String
    Public Property InstalledPath As String

    Public Property Type As String

    Public Property Name As String

End Class